/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.persistence.hibernate.core;

import com.blandware.atleap.common.util.ConvertUtil;
import com.blandware.atleap.common.util.PartialCollection;
import com.blandware.atleap.common.util.QueryInfo;
import com.blandware.atleap.model.core.ActionPage;
import com.blandware.atleap.model.core.ContentPage;
import com.blandware.atleap.model.core.Page;
import com.blandware.atleap.model.core.Role;
import com.blandware.atleap.model.core.statistics.Visit;
import com.blandware.atleap.model.news.NewsItem;
import com.blandware.atleap.persistence.core.PageDAO;
import com.blandware.atleap.persistence.core.StatisticsDAO;
import com.blandware.atleap.persistence.util.PageAndAnnotation;

import java.util.*;

/**
 * <p>Hibernate implementation of PageDAO</p>
 * <p><a href="PageDAOHibernate.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.36 $ $Date: 2008/06/23 13:24:54 $
 */
public class PageDAOHibernate extends LocalizableDAOHibernate implements PageDAO {

    /**
     * DAO to work with statistics
     */
    protected StatisticsDAO statisticsDAO = null;

    /**
     * Sets DAO to work with statistics.
     *
     * @param statisticsDAO DAO to set
     */
    public void setStatisticsDAO(StatisticsDAO statisticsDAO) {
        this.statisticsDAO = statisticsDAO;
    }

	/**
	 * Creates new instance of PageDAOHibernate
	 */
	public PageDAOHibernate() {
	}

	/**
	 * @see com.blandware.atleap.persistence.core.PageDAO#retrievePage(Long)
	 */
	public Page retrievePage(Long pageId) {
		return (Page) getHibernateTemplate().get(Page.class, pageId);
	}

	/**
	 * @see com.blandware.atleap.persistence.core.PageDAO#findPageByUri(java.lang.String)
	 */
	public Page findPageByUri(String uri) {
		return (Page) findUniqueResult("from Page p where p.uri = ?", new Object[]{uri});
	}

	/**
	 * @see com.blandware.atleap.persistence.core.PageDAO#updatePage(com.blandware.atleap.model.core.Page)
	 */
	public void updatePage(Page page) {
		getHibernateTemplate().update(page);
	}

	/**
	 * @see com.blandware.atleap.persistence.core.PageDAO#listLinkableItems(com.blandware.atleap.common.util.QueryInfo)
	 */
	public PartialCollection listLinkableItems(QueryInfo queryInfo) {
		String whereClause = "";
		String orderByClause = "";
		if ( queryInfo != null ) {
			whereClause = queryInfo.getWhereClause();
			orderByClause = queryInfo.getOrderByClause();
			if ( whereClause == null || whereClause.length() == 0 ) {
				whereClause = "";
			}
			if ( orderByClause != null && orderByClause.length() != 0 ) {
				orderByClause = " order by " + orderByClause;
			} else {
				orderByClause = "";
			}
		}

		List list;
		Integer total = null;
		String localeIdentifier = null;
		if ( queryInfo != null ) {
			if ( queryInfo.getQueryParameters() != null ) {
				localeIdentifier = (String) queryInfo.getQueryParameters().get("localeIdentifier");
			}
		}

		boolean localeIdentifierPresent = localeIdentifier != null && localeIdentifier.length() > 0;

		String hqlPart;
		ArrayList args = new ArrayList();
		if ( localeIdentifierPresent ) {
			args.add(localeIdentifier);
			hqlPart = new StringBuffer("from Page as page ")
                    .append("left outer join page.contentFields as titleField ")
                    .append("left outer join titleField.contentFieldValues as title ")
                    .append("left outer join page.roles as role ").append("where ((page.className != '").append(ActionPage.class.getName()).append("') and (page.className != '").append(ContentPage.class.getName()).append("')) and ")
			        .append("titleField.identifier = 'title' ")
			        .append("and title.contentLocale.identifier = ? ")
			        .toString();
			if ( whereClause.length() > 0 ) {
				hqlPart += "and " + whereClause;
			}
		} else {
			hqlPart = new StringBuffer("from Page page left outer join page.roles as role ").append("where ((page.className != '").append(ActionPage.class.getName()).append("') and (page.className != '").append(ContentPage.class.getName()).append("')) ")
			        .toString();
			if ( whereClause.length() > 0 ) {
				hqlPart += "and " + whereClause;
			}
		}

		if ( queryInfo != null && (queryInfo.getLimit() != null || queryInfo.getOffset() != null) ) {
			// query count
			String hqlForTotal = "select count(distinct page.id) " + hqlPart;
			total = findUniqueIntegerResult(hqlForTotal, args.toArray());
			if ( total == null ) {
				total = new Integer(0);
			}
		}

        // If we don't have any info about the total number of results yet or
        // we know that there's something that will be found, then fetch data
		if ( total == null || total.intValue() > 0 ) {
			String hql;
			if ( localeIdentifierPresent ) {
				hql = "select distinct page, title.simpleValue " + hqlPart + orderByClause;
			} else {
				hql = "select page " + hqlPart + orderByClause;
			}
			list = executeFind(hql, queryInfo, args.toArray());
			if ( total == null ) {
				total = new Integer(list.size());
			}
			if ( localeIdentifierPresent ) {
				for ( ListIterator i = list.listIterator(); i.hasNext(); ) {
					Object[] objects = (Object[]) i.next();
					Page page = (Page) objects[0];
					page.getTitle().put(localeIdentifier, objects[1]);
					i.set(page);
				}
			}
		} else {
			list = new ArrayList();
		}


		return new PartialCollection(list, total);
	}

	/**
	 * @see com.blandware.atleap.persistence.core.PageDAO#listPages(com.blandware.atleap.common.util.QueryInfo) 
	 */
	public PartialCollection listPages(QueryInfo queryInfo) {
		String whereClause = "";
		String orderByClause = "";
		if ( queryInfo != null ) {
			whereClause = queryInfo.getWhereClause();
			orderByClause = queryInfo.getOrderByClause();
			if ( whereClause == null || whereClause.length() == 0 ) {
				whereClause = "";
			}
			if ( orderByClause != null && orderByClause.length() != 0 ) {
				orderByClause = " order by " + orderByClause;
			} else {
				orderByClause = "";
			}
		}

		List list;
		Integer total = null;
		String localeIdentifier = null;
		if ( queryInfo != null ) {
			if ( queryInfo.getQueryParameters() != null ) {
				localeIdentifier = (String) queryInfo.getQueryParameters().get("localeIdentifier");
			}
		}

		boolean localeIdentifierPresent = localeIdentifier != null && localeIdentifier.length() > 0;

		String hqlPart;
		ArrayList args = new ArrayList();
		if ( localeIdentifierPresent ) {
			args.add(localeIdentifier);
			hqlPart = new StringBuffer("from Page as page ")
                    .append("left outer join page.contentFields as titleField ")
                    .append("left outer join titleField.contentFieldValues as title ")
                    .append("left outer join page.roles as role ")
                    .append("where ")
			        .append("titleField.identifier = 'title' ")
			        .append("and title.contentLocale.identifier = ? ")
                    .append("and page.active = 'T' ")
                    .toString();
			if ( whereClause.length() > 0 ) {
				hqlPart += "and " + whereClause;
			}
		} else {
			hqlPart = new StringBuffer("from Page page left outer join page.roles as role ")
                    .append("where page.active = 'T' ")
			        .toString();
			if ( whereClause.length() > 0 ) {
				hqlPart += "and " + whereClause;
			}
		}

		if ( queryInfo != null && (queryInfo.getLimit() != null || queryInfo.getOffset() != null) ) {
			// query count
			String hqlForTotal = "select count(distinct page.id) " + hqlPart;
			total = findUniqueIntegerResult(hqlForTotal, args.toArray());
			if ( total == null ) {
				total = new Integer(0);
			}
		}

        // If we don't have any info about the total number of results yet or
        // we know that there's something that will be found, then fetch data
		if ( total == null || total.intValue() > 0 ) {
			String hql;
			if ( localeIdentifierPresent ) {
				hql = "select distinct page, title.simpleValue " + hqlPart + orderByClause;
			} else {
				hql = "select page " + hqlPart + orderByClause;
			}
			list = executeFind(hql, queryInfo, args.toArray(), true, "query.listPages");
			if ( total == null ) {
				total = new Integer(list.size());
			}
			if ( localeIdentifierPresent ) {
				for ( ListIterator i = list.listIterator(); i.hasNext(); ) {
					Object[] objects = (Object[]) i.next();
					Page page = (Page) objects[0];
					page.getTitle().put(localeIdentifier, objects[1]);
					i.set(page);
				}
			}
		} else {
			list = new ArrayList();
		}

		return new PartialCollection(list, total);
	}

    /**
     * @see com.blandware.atleap.persistence.core.PageDAO#listPagesFetching()
     */
    public Collection listPagesFetching() {
        String hql = new StringBuffer("from Page as page ")
                .append("left outer join fetch page.contentFields as field ")
                .append("left outer join fetch field.contentFieldValues as value where page.active = 'T'").toString();
        return new HashSet(executeFind(hql));
    }

    /**
     * @see com.blandware.atleap.persistence.core.PageDAO#getLastPages(com.blandware.atleap.common.util.QueryInfo)
     */
    public List getLastPages(QueryInfo queryInfo) {

        boolean skipNewsItems = false;
        if (queryInfo.getQueryParameters() != null) {
            skipNewsItems = Boolean.TRUE.equals(queryInfo.getQueryParameters().get("skipNewsItems"));
        }
        boolean skipActionPages = false;
        if (queryInfo.getQueryParameters() != null) {
            skipActionPages = Boolean.TRUE.equals(queryInfo.getQueryParameters().get("skipActionPages"));
        }

        String whereClause = "page.active = 'T' ";
        if (skipNewsItems) {
            whereClause += " and page.className != '" + NewsItem.class.getName() + "' ";
        }
        if (skipActionPages) {
            whereClause += " and page.className != '" + ActionPage.class.getName() + "' ";
        }
        String orderByClause = " order by page.lastUpdatedDatetime desc, title.simpleValue asc";

        List list = new ArrayList();

		String localeIdentifier = null;
		List roles = null;
		Integer limit = queryInfo.getLimit();
        if ( queryInfo.getQueryParameters() != null ) {
            localeIdentifier = (String) queryInfo.getQueryParameters().get("localeIdentifier");
            roles = (List) queryInfo.getQueryParameters().get("roles");
        }

        boolean localeIdentifierPresent = localeIdentifier != null && localeIdentifier.length() > 0;

		String hqlPart;
		ArrayList args = new ArrayList();
		if ( localeIdentifierPresent ) {
			args.add(localeIdentifier);
			hqlPart = new StringBuffer("from Page as page ")
			        .append("left outer join page.contentFields as titleField ")
			        .append("left outer join titleField.contentFieldValues as title ")
			        .append("where ")
			        .append("titleField.identifier = 'title' ")
			        .append("and title.contentLocale.identifier = ? ")
			        .append("and ").append(whereClause)
			        .toString();
		} else {
			hqlPart = "from Page page where " + whereClause;
		}

		String hql;
		if ( localeIdentifierPresent ) {
			hql = "select page, title.simpleValue " + hqlPart + orderByClause;
		} else {
			hql = "select page " + hqlPart + orderByClause;
		}

		Integer offset = new Integer(0);
		// Iterate until requested number of elements will be acquired.
        // This tricky loop is needed because we cannot predict how much of
        // pages we'll need to fetch (as some of them don't match to the given
        // roles).
		do {
			List tmp = executeFind(hql, queryInfo, args.toArray(), true, "query.getLastPages");

			// if there are no more elements, break iteration
			if ( tmp.isEmpty() ) {
				break;
			}

			if ( localeIdentifierPresent ) {
				for ( ListIterator i = tmp.listIterator(); i.hasNext(); ) {
					Object[] objects = (Object[]) i.next();
					Page page = (Page) objects[0];
					List pageRoles = page.getRoles();
					boolean accessAllowed = true;
					if ( !pageRoles.isEmpty() ) {
						accessAllowed = false;
						if ( roles != null ) {
							for ( Iterator j = pageRoles.iterator(); j.hasNext() && !accessAllowed; ) {
								Role role = (Role) j.next();
								accessAllowed = roles.contains(role);
							}
						}
					}
					if ( !accessAllowed ) {
						i.remove();
					} else {
						page.getTitle().put(localeIdentifier, objects[1]);
						i.set(page);
					}
				}
			} else {
				// filter by roles
				for ( Iterator i = tmp.iterator(); i.hasNext(); ) {
					Page page = (Page) i.next();
					if ( !page.getRoles().equals(roles) ) {
						i.remove();
					}
				}
			}

			list.addAll(tmp);

			offset = new Integer(offset.intValue() + limit.intValue());
			queryInfo.setOffset(offset);
		} while ( list.size() < limit.intValue() );

		if ( list.size() > limit.intValue() ) {
			list = list.subList(0, limit.intValue());
		}

        return list;
    }

    /**
     * @see com.blandware.atleap.persistence.core.PageDAO#getPagesAnnotations(java.util.List, String)  
     */
    public List getPagesAnnotations(List list, String localeIdentifier) {
        boolean localeIdentifierPresent = localeIdentifier != null && localeIdentifier.length() > 0;
        ArrayList args;
        StringBuffer inSpec = new StringBuffer();
        inSpec.append('(');
        for (Iterator i = list.iterator(); i.hasNext(); ) {
            Page page = (Page) i.next();
            inSpec.append(page.getId());
            if (i.hasNext()) {
                inSpec.append(',');
            }
        }
        inSpec.append(')');

        args = new ArrayList();
        StringBuffer hqlBuffer = new StringBuffer();
        if (localeIdentifierPresent) {
            hqlBuffer.append("select field.owner.id, fieldValue.value ")
                    .append("from ContentField field ")
                    .append("left outer join field.contentFieldValues fieldValue ")
                    .append("where field.identifier = 'annotation' ")
                    .append("and fieldValue.contentLocale.identifier = ? ")
                    .append("and field.owner.id in ").append(inSpec);
            args.add(localeIdentifier);
        } else {
            hqlBuffer.append("select field.owner.id, fieldValue.value ")
                    .append("from ContentField field ")
                    .append("left outer join field.contentFieldValues fieldValue ")
                    .append("where field.identifier = 'annotation' ")
                    .append("and field.owner.id in ").append(inSpec);
        }

        List tuples = executeFind(hqlBuffer.toString(), args.toArray(new Object[args.size()]));

        Map idToAnnotation = new HashMap();
        for (Iterator i = tuples.iterator(); i.hasNext(); ) {
            Object[] tuple = (Object[]) i.next();
            Long id = (Long) tuple[0];
            if (idToAnnotation.get(id) == null) {
                byte[] bytes = (byte[]) tuple[1];
                String annotation = ConvertUtil.convertToString(bytes);
                idToAnnotation.put(id, annotation);
            }
        }

        // now idToAnnotation contains mapping from ids to annotations for pages
        // for which annotations exist

        List result = new ArrayList(list.size());
        for (ListIterator i = list.listIterator(); i.hasNext(); ) {
            Page page = (Page) i.next();
            String annotation;
            if (idToAnnotation.get(page.getId()) != null) {
                annotation = (String) idToAnnotation.get(page.getId());
            } else {
                annotation = null;
            }
            PageAndAnnotation pageAndAnnotation = new PageAndAnnotation();
            pageAndAnnotation.setPage(page);
            pageAndAnnotation.setAnnotation(annotation);
            result.add(pageAndAnnotation);
        }

        return result;
    }

    /**
	 * @see com.blandware.atleap.persistence.core.PageDAO#hasDuplicates(com.blandware.atleap.model.core.Page)
	 */
	public boolean hasDuplicates(Page page) {

		ArrayList args = new ArrayList();
		args.add(page.getUri());
		String hql = "select count(p.id) from Page p where p.uri = ?";
		if ( page.getId() != null ) {
			hql += " and p.id != ?";
			args.add(page.getId());
		}

		int count = (findUniqueIntegerResult(hql, args.toArray())).intValue();

		return count > 0;

	}

    /**
     * @see com.blandware.atleap.persistence.core.PageDAO#removeAssociations(com.blandware.atleap.model.core.Page)
     */
    public void removeAssociations(Page page) {
        List visits = statisticsDAO.listPageVisits(page.getId());
        for (int i = 0; i < visits.size(); i++) {
            Visit visit = (Visit) visits.get(i);
            visit.setPage(null);
            statisticsDAO.updateVisit(visit);
        }
    }

	/**
	 * Finds content page by id in list of pages
	 *
	 * @param pages list to search
	 * @param id           ID of page
	 * @return page
	 */
	protected Page findPageById(List pages, Long id) {
		for ( int i = 0; i < pages.size(); i++ ) {
			Page page = (Page) pages.get(i);
			if ( page.getId().equals(id) ) {
				return page;
			}
		}
		return null;
	}

	/**
	 * Collapses list by setting up for different pages and locales into maps
	 *
	 * @param queryResult query result with 5 objects [page, locale, title,
     * description, keywords]
	 * @return collapsed list
	 */
	protected List setLocalizableFields(List queryResult) {
		if ( queryResult == null || queryResult.size() <= 0 ) {
			return null;
		}

        // First make a list of pages from quintuples (assigning title, description
        // and keywords values with specified locale)
		List pages = new ArrayList(queryResult.size());
		for ( int i = 0; i < queryResult.size(); i++ ) {
			Object[] objects = (Object[]) queryResult.get(i);
			Page page = (Page) objects[0];
			String locale = (String) objects[1];
			if ( locale != null && locale.trim().length() > 0 ) {
				page.getTitle().put(locale, objects[2]);
				page.getDescription().put(locale, objects[3]);
				page.getKeywords().put(locale, objects[4]);
			}
			pages.add(page);
		}

        // Now make a such list of pages that each page is encountered maximum
        // one time (if some page is encountered second time, fore example, its
        // internal fields' info is added to previous instance)
		List fullPages = new ArrayList(pages.size());
		for ( int i = 0; i < pages.size(); i++ ) {
			Page newPage = (Page) pages.get(i);
			Page existingPage = findPageById(fullPages, newPage.getId());
			if ( existingPage != null ) {
				//title
				Iterator iterator = newPage.getTitle().keySet().iterator();
				while ( iterator.hasNext() ) {
					String localeIdentifier = (String) iterator.next();
					String value = (String) newPage.getTitle().get(localeIdentifier);
					if ( value == null ) {
						continue;
					}
					existingPage.getTitle().put(localeIdentifier, value);
				}

				//description
				iterator = newPage.getDescription().keySet().iterator();
				while ( iterator.hasNext() ) {
					String localeIdentifier = (String) iterator.next();
					String value = (String) newPage.getDescription().get(localeIdentifier);
					if ( value == null ) {
						continue;
					}
					existingPage.getDescription().put(localeIdentifier, value);
				}

				//keywords
				iterator = newPage.getKeywords().keySet().iterator();
				while ( iterator.hasNext() ) {
					String localeIdentifier = (String) iterator.next();
					String value = (String) newPage.getKeywords().get(localeIdentifier);
					if ( value == null ) {
						continue;
					}
					existingPage.getKeywords().put(localeIdentifier, value);
				}

			} else {
				fullPages.add(newPage);
			}
		}

		return fullPages;
	}
}
